<?php

/* ========================================================================= */
/* This file is part of the HypnoPHP Framework Library                       */
/* Copyright (c) 2009-2010, Radu Buzea                                       */
/* ========================================================================= */

/**
* @author Radu Buzea
* @copyright Copyright (c) 2009-2010, Radu Buzea (www.hypnosis.ro)
* @license GNU Lesser General Public License (www.gnu.org/copyleft/lesser.html)
* @package HypnoPHP
* @category Core
* @version 1.0.3
*/

final class HBenchmark
{

	public $backtrace = array();

	# ###########################################################################

	/**
	* Create an HTML table with all traces from the benchmark list
	*
	*	@return string
	*/
	static public function getBacktrace()
	{
		$mem_usage = memory_get_usage(false);
		$mem_usage_real = memory_get_usage(true);
		$mem_peak_usage = memory_get_peak_usage(false);
		$mem_peak_usage_real = memory_get_peak_usage(true);

		$this_time = microtime();
		$benchmark = self::singleton();
		$backtrace = $benchmark->backtrace;
		if (!is_array($backtrace) || count($backtrace) == 0) { return '[NO-TRACES]'; }

		$output = "<style type=\"text/css\">" . PHP_EOL .
			".btcell{font:12px Verdana,Arial,sans-serif;padding:2px 6px;cursor:default;color:black;}" . PHP_EOL .
			".btcellm{font:13px 'Courier New',Courier,monospace;padding:2px 6px;cursor:default;color:black;}" . PHP_EOL .
			"</style>" . PHP_EOL .
			"<table border=\"0\" cellpadding=\"2\" cellspacing=\"1\" style=\"border:1px solid #a0a0a0\"><tbody>" . PHP_EOL .
			"<tr style=\"background-color:#f0e0a0\">" . PHP_EOL .
			"<td height=\"22\" class=\"btcell\" align=\"right\"><b>#</b></td>" . PHP_EOL .
			"<td height=\"22\" class=\"btcell\"><b>Class (obj. type)</b></td>" . PHP_EOL .
			"<td height=\"22\" class=\"btcell\"><b>Function</b></td>" . PHP_EOL .
			"<td height=\"22\" class=\"btcell\" align=\"center\"><b>Type</b></td>" . PHP_EOL .
			"<td height=\"22\" class=\"btcell\"><b>C. Time</b></td>" . PHP_EOL .
			"<td height=\"22\" class=\"btcell\"><b>T. Time</b></td>" . PHP_EOL .
			"</tr>" . PHP_EOL;
		$last_time = 0;
		foreach ($backtrace as $t => $trace) {
			$bgcolor = ($t % 2) ? 'fffcda' : 'faf0c0';
			$output_part = "<tr style=\"background-color:#{$bgcolor}\">" . PHP_EOL .
				"<td class=\"btcell\" align=\"right\">" . ( $t + 1) . "</td>" . PHP_EOL;
			if (isset($trace['class'])) {
				if ($trace['class'] == 'HBenchmark') { continue; }
			}
			$object_in = false;
			if (isset($trace['object']) && is_object($trace['object'])) {
				$output_part .= "<td class=\"btcell\">" . get_class($trace['object']) . "</td>" . PHP_EOL;
				$object_in = true;
			}
			if (!$object_in && isset($trace['class']) && $trace['class'] != '') {
				$output_part .= "<td class=\"btcell\">" . $trace['class'] . "</td>" . PHP_EOL;
				$object_in = true;
			}
			if (!$object_in) {
				$output_part .= "<td class=\"btcell\" style=\"color:gray\">N/A</td>" . PHP_EOL;
			}
			$comment_text = 'Line ' . $trace['line'] . ' in file: ' . $trace['file'];
			if (isset($trace['function'])) {
				$output_part .= "<td class=\"btcellm\" title=\"{$comment_text}\" style=\"cursor:help\">" .
					$trace['function'] . '(';
				if (isset($trace['args']) && is_array($trace['args'])) {
					foreach ($trace['args'] as $k => $arg) {
						if ($k > 0) { $output_part .= ', '; }
						switch (gettype($arg)) {
							case 'boolean':
								$output_part .= "<span style=\"color:#6c2500\">";
								$output_part .= $arg ? 'true' : 'false';
								$output_part .= "</span>";
								break;
							case 'integer':
							case 'double':
								$output_part .= "<span style=\"color:#8c008c\">$arg</span>";
								break;
							case 'string':
								if (strlen($arg) > 32) { $arg = substr($arg, 0, 32) . '...'; }
								$output_part .= "<span style=\"color:#0020bc\">'" . htmlentities(addslashes($arg)) . "'</span>";
								break;
							case 'object':
								$output_part .= "<span style=\"color:#006c2c\">object[" . get_class($arg) . "]</span>";
								break;
							case 'resource':
								$output_part .= "<span style=\"color:#006c2c\">resource[" . intval($arg) . "]</span>";
								break;
							case 'array':
								$output_part .= "<span style=\"color:#006c2c\">array[" . count($arg) . "]</span>";
								break;
						}
					}
				}
				$output_part .= ")</td>" . PHP_EOL;
			}
			if (isset($trace['type'])) {
				if ($trace['type'] == '::') {
					$output_part .= "<td class=\"btcell\" align=\"center\">static call</td>" . PHP_EOL;
				} else {
					$output_part .= "<td class=\"btcell\" align=\"center\">call</td>" . PHP_EOL;
				}
			} else {
				$output_part .= "<td class=\"btcell\" align=\"center\">call</td>" . PHP_EOL;
			}
			if ($t == 0) {
				$last_time = 0;
				$output_part .= "<td class=\"btcell\">0:0000</td>" . PHP_EOL;
				$output_part .= "<td class=\"btcell\">0:0000</td>" . PHP_EOL;
			} else {
				$prev_time = round($backtrace[$t - 1]['time'], 4);
				$next_time = round($trace['time'], 4);
				if ($next_time > $prev_time) {
					$time = round($next_time - $prev_time, 4);
					$last_time = round($last_time + ($next_time - $prev_time), 4);
				} else {
					$time = 0;
				}
				if ($time == 0) {
					$output_part .= "<td class=\"btcell\" style=\"color:gray\">" .
						number_format($time, 4, ':', '') . "</td>" . PHP_EOL;
					$output_part .= "<td class=\"btcell\" style=\"color:gray\">" .
						number_format($last_time, 4, ':', '') . "</td>" . PHP_EOL;
				} else {
					$output_part .= "<td class=\"btcell\">" .
						number_format($time, 4, ':', '') . "</td>" . PHP_EOL;
					$output_part .= "<td class=\"btcell\">" .
						number_format($last_time, 4, ':', '') . "</td>" . PHP_EOL;
				}
			}
			$output .= $output_part . "</tr>" . PHP_EOL;
		}
		$t++;
		$bgcolor = ($t % 2) ? 'fffbcb' : 'faf0c0';
		$output .= "<tr style=\"background-color:#{$bgcolor}\">" . PHP_EOL .
			"<td class=\"btcell\" align=\"right\">" . ( $t + 1) . "</td>" . PHP_EOL;
		$this_time = microtime() - $this_time;
		$last_time = $last_time + $this_time;
		$output .= "<td class=\"btcell\">HBenchmark</td>" . PHP_EOL .
			"<td class=\"btcellm\">getBacktrace()</td>" . PHP_EOL .
			"<td class=\"btcell\" align=\"center\">call</td>" . PHP_EOL .
			"<td class=\"btcell\">" . number_format($this_time, 4, ':', '') . "</td>" . PHP_EOL .
			"<td class=\"btcell\">" . number_format($last_time, 4, ':', '') . "</td>" . PHP_EOL .
			"</tr>" . PHP_EOL . "</tbody></table>" . PHP_EOL;

		$mem_usage_short = '--';
		if ($mem_usage > 1024) {
			$mem_usage_short = round($mem_usage / 1024, 2);
			if ($mem_usage_short > 1024) {
				$mem_usage_short = round($mem_usage_short / 1024, 2) . ' Mb';
			} else {
				$mem_usage_short .= 'Kb';
			}
		}
		$mem_usage_real_short = '--';
		if ($mem_usage_real > 1024) {
			$mem_usage_real_short = round($mem_usage_real / 1024, 2);
			if ($mem_usage_real_short > 1024) {
				$mem_usage_real_short = round($mem_usage_real_short / 1024, 2) . ' Mb';
			} else {
				$mem_usage_real_short .= 'Kb';
			}
		}
		$mem_peak_usage_short = '--';
		if ($mem_peak_usage > 1024) {
			$mem_peak_usage_short = round($mem_peak_usage / 1024, 2);
			if ($mem_peak_usage_short > 1024) {
				$mem_peak_usage_short = round($mem_peak_usage_short / 1024, 2) . ' Mb';
			} else {
				$mem_peak_usage_short .= 'Kb';
			}
		}
		$mem_peak_usage_real_short = '--';
		if ($mem_peak_usage_real > 1024) {
			$mem_peak_usage_real_short = round($mem_peak_usage_real / 1024, 2);
			if ($mem_peak_usage_real_short > 1024) {
				$mem_peak_usage_real_short = round($mem_peak_usage_real_short / 1024, 2) . ' Mb';
			} else {
				$mem_peak_usage_real_short .= 'Kb';
			}
		}
		$output .= "<table border=\"0\" cellpadding=\"2\" cellspacing=\"1\" style=\"margin-top:10px;border:1px solid #a0a0a0\"><tbody>" . PHP_EOL .
			"<tr>"  . PHP_EOL . "<td class=\"btcell\" style=\"height:22px;background-color:#f0e0a0\">Memory usage</td>" . PHP_EOL .
			"<td class=\"btcell\" style=\"background-color:#faf0c0\">" .
			number_format($mem_usage, 0, '.', ' ') . " bytes ({$mem_usage_short})</td>" . PHP_EOL .
			"<td class=\"btcell\" style=\"background-color:#fffcda\">" .
			number_format($mem_usage_real, 0, '.', ' '). " bytes Real usage ({$mem_usage_real_short})</td>" . PHP_EOL .
			"</tr><tr>" . PHP_EOL . "<td class=\"btcell\" style=\"height:22px;background-color:#f0e0a0\">Memory peak usage</td>" . PHP_EOL .
			"<td class=\"btcell\" style=\"background-color:#faf0c0\">" .
			number_format($mem_peak_usage, 0, '.', ' '). " bytes ({$mem_peak_usage_short})</td>" . PHP_EOL .
			"<td class=\"btcell\" style=\"background-color:#fffcda\">" .
			number_format($mem_peak_usage_real, 0, '.', ' ') . " bytes Real usage ({$mem_peak_usage_real_short})</td>" . PHP_EOL .
			"</tr>" . PHP_EOL . "</tbody></table>" . PHP_EOL;

		return $output;
	}

	# ###########################################################################

	/**
	* Print the HTML table with all traces from the benchmark list
	* created in 'get_backtrace' method
	*
	*	@return void
	*/
	static public function printBacktrace()
	{
		$bt_array = HBenchmark::getBacktrace();
		print($bt_array);
	}

	# ###########################################################################

	/**
	* Add a trace point for benchmarking
	*
	*	@return void
	*/
	static public function tracePoint()
	{
		$debug_backtrace = debug_backtrace();
		if (count($debug_backtrace) > 0 && isset($debug_backtrace[1])) {
			$benchmark = self::singleton();
			if (!isset($benchmark->backtrace) || !is_array($benchmark->backtrace)) {
				$benchmark->backtrace = array();
			}
			$entry = $debug_backtrace[1];
			$backtrace = $benchmark->backtrace;
			$entry['time'] = microtime(true);
			$backtrace[] = $entry;
			$benchmark->backtrace = $backtrace;
		}
	}

	# ###########################################################################

	/**
	* Sigleton implementation
	*
	* @return object (HBenchmark)
	*/
	static public function &singleton()
	{
		static $instances = array();
		$class = __CLASS__;
		if (!array_key_exists($class, $instances)) {
			$instances[$class] = new $class;
		}
		$singleton =& $instances[$class];
		return $singleton;
	}

	# ###########################################################################

}
